<template>
    <div class="echarts" :style="`height: ${height}px;width: ${width}px`"/>
    <!--https://github.com/ecomfe/vue-echarts/blob/master/README.zh_CN.md-->
</template>

<style>
    .echarts {
        display: inline-block;
        position: relative;
        border: 1px solid rgba(0,0,0,0.05);
        border-radius: 8px;
        box-shadow: 4px 4px 40px rgba(0, 0, 0, .1);
        padding: 1.5em 2em;
        min-width: calc(35vw + 4em);
        box-sizing: border-box;
    }
</style>

<script>
    import echarts from 'echarts/lib/echarts'
    import debounce from 'lodash/debounce'
    import { addListener, removeListener } from 'resize-detector'

    // enumerating ECharts events for now
    const EVENTS = [
        'legendselectchanged',
        'legendselected',
        'legendunselected',
        'legendscroll',
        'datazoom',
        'datarangeselected',
        'timelinechanged',
        'timelineplaychanged',
        'restore',
        'dataviewchanged',
        'magictypechanged',
        'geoselectchanged',
        'geoselected',
        'geounselected',
        'pieselectchanged',
        'pieselected',
        'pieunselected',
        'mapselectchanged',
        'mapselected',
        'mapunselected',
        'axisareaselected',
        'focusnodeadjacency',
        'unfocusnodeadjacency',
        'brush',
        'brushselected',
        'rendered',
        'finished',
        'click',
        'dblclick',
        'mouseover',
        'mouseout',
        'mousemove',
        'mousedown',
        'mouseup',
        'globalout',
        'contextmenu'
    ]

    export default {
        name: "XEcharts",
        props: {
            width: { type: String, default: '600' },
            height: { type: String, default: '400' },
            options: Object, /* ECharts 实例的数据。修改这个 prop 会触发 ECharts 实例的 setOption 方法 */
            theme: [String, Object], /* 当前 ECharts 实例使用的主题 */
            initOptions: Object, /* 用来初始化 ECharts 实例 */
            group: String, /* 实例的分组，会自动绑定到 ECharts 组件的同名属性上 */
            autoresize: Boolean, /* 默认false，这个 prop 用来指定 ECharts 实例在组件根元素尺寸变化时是否需要自动进行重绘 */
            watchShallow: Boolean,
            manualUpdate: Boolean /* 在性能敏感（数据量很大）的场景下，我们最好对于 options prop 绕过 Vue 的响应式系统。当将 manual-update prop 指定为 true 且不传入 options prop 时，数据将不会被监听。然后，你需要用 ref 获取组件实例以后手动调用 mergeOptions 方法来更新图表。 */
        },
        data () {
            return {
                lastArea: 0
            }
        },
        watch: {
            group (group) {
                this.chart.group = group
            }
        },
        methods: {
            // provide a explicit merge option method
            mergeOptions (options, notMerge, lazyUpdate) {
                if (this.manualUpdate) {
                    this.manualOptions = options
                }

                if (!this.chart) {
                    this.init()
                } else {
                    this.delegateMethod('setOption', options, notMerge, lazyUpdate)
                }
            },
            // just delegates ECharts methods to Vue component
            // use explicit params to reduce transpiled size for now
            appendData (params) {
                this.delegateMethod('appendData', params)
            },
            resize (options) {
                this.delegateMethod('resize', options)
            },
            dispatchAction (payload) {
                this.delegateMethod('dispatchAction', payload)
            },
            convertToPixel (finder, value) {
                return this.delegateMethod('convertToPixel', finder, value)
            },
            convertFromPixel (finder, value) {
                return this.delegateMethod('convertFromPixel', finder, value)
            },
            containPixel (finder, value) {
                return this.delegateMethod('containPixel', finder, value)
            },
            showLoading (type, options) {
                this.delegateMethod('showLoading', type, options)
            },
            hideLoading () {
                this.delegateMethod('hideLoading')
            },
            getDataURL (options) {
                return this.delegateMethod('getDataURL', options)
            },
            getConnectedDataURL (options) {
                return this.delegateMethod('getConnectedDataURL', options)
            },
            clear () {
                this.delegateMethod('clear')
            },
            dispose () {
                this.delegateMethod('dispose')
            },
            delegateMethod (name, ...args) {
                if (!this.chart) {
                    this.init()
                }
                return this.chart[name](...args)
            },
            delegateGet (name, method) {
                if (!this.chart) {
                    this.init()
                }
                return this.chart[method]()
            },
            getArea () {
                return this.$el.offsetWidth * this.$el.offsetHeight
            },
            init () {
                if (this.chart) {
                    return
                }

                let chart = echarts.init(this.$el, this.theme, this.initOptions)

                if (this.group) {
                    chart.group = this.group
                }

                chart.setOption(this.manualOptions || this.options || {}, true)

                // expose ECharts events as custom events
                EVENTS.forEach(event => {
                    chart.on(event, params => {
                        this.$emit(event, params)
                    })
                })

                if (this.autoresize) {
                    this.lastArea = this.getArea()
                    this.__resizeHandler = debounce(() => {
                        if (this.lastArea === 0) {
                            // emulate initial render for initially hidden charts
                            this.mergeOptions({}, true)
                            this.resize()
                            this.mergeOptions(this.options || this.manualOptions || {}, true)
                        } else {
                            this.resize()
                        }
                        this.lastArea = this.getArea()
                    }, 100, { leading: true })
                    addListener(this.$el, this.__resizeHandler)
                }

                Object.defineProperties(this, {
                    // Only recalculated when accessed from JavaScript.
                    // Won't update DOM on value change because getters
                    // don't depend on reactive values
                    width: {
                        configurable: true,
                        get: () => {
                            return this.delegateGet('width', 'getWidth')
                        }
                    },
                    height: {
                        configurable: true,
                        get: () => {
                            return this.delegateGet('height', 'getHeight')
                        }
                    },
                    isDisposed: {
                        configurable: true,
                        get: () => {
                            return !!this.delegateGet('isDisposed', 'isDisposed')
                        }
                    },
                    computedOptions: {
                        configurable: true,
                        get: () => {
                            return this.delegateGet('computedOptions', 'getOption')
                        }
                    }
                })

                this.chart = chart
            },
            destroy () {
                if (this.autoresize) {
                    removeListener(this.$el, this.__resizeHandler)
                }
                this.dispose()
                this.chart = null
            },
            refresh () {
                if (this.chart) {
                    this.destroy()
                    this.init()
                }
            }
        },
        created () {
            if (!this.manualUpdate) {
                this.$watch('options', (val, oldVal) => {
                    if (!this.chart && val) {
                        this.init()
                    } else {
                        // mutating `options` will lead to merging
                        // replacing it with new reference will lead to not merging
                        // eg.
                        // `this.options = Object.assign({}, this.options, { ... })`
                        // will trigger `this.chart.setOption(val, true)
                        // `this.options.title.text = 'Trends'`
                        // will trigger `this.chart.setOption(val, false)`
                        this.chart.setOption(val, val !== oldVal)
                    }
                }, { deep: !this.watchShallow })
            }

            let watched = ['theme', 'initOptions', 'autoresize', 'manualUpdate', 'watchShallow']
            watched.forEach(prop => {
                this.$watch(prop, () => {
                    this.refresh()
                }, { deep: true })
            })
        },
        mounted () {
            // auto init if `options` is already provided
            if (this.options) {
                this.init()
            }
        },
        activated () {
            if (this.autoresize) {
                this.chart && this.chart.resize()
            }
        },
        beforeDestroy () {
            if (!this.chart) {
                return
            }
            this.destroy()
        },
        connect (group) {
            if (typeof group !== 'string') {
                group = group.map(chart => chart.chart)
            }
            echarts.connect(group)
        },
        disconnect (group) {
            echarts.disConnect(group)
        },
        registerMap (mapName, geoJSON, specialAreas) {
            echarts.registerMap(mapName, geoJSON, specialAreas)
        },
        registerTheme (name, theme) {
            echarts.registerTheme(name, theme)
        },
        graphic: echarts.graphic
    }
</script>
